/*
 * Copyright 2019 Flipkart Internet Pvt. Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.flipkart.ohos.proteus.toolbox;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorProperty;
import ohos.app.Context;

import com.flipkart.ohos.proteus.LogUtil;
import com.flipkart.ohos.proteus.ProteusConstants;
import com.flipkart.ohos.proteus.value.ObjectValue;
import com.flipkart.ohos.proteus.value.Value;

import java.util.Iterator;

/**
 * Defines common utilities for working with AnimatorPropertys.
 */
public class AnimatorPropertyUtils {
    private static final String TAG = "AnimatorPropertyUtils";
    private static final String ACCELERATE = "accelerate";
    private static final String ACCELERATE_DECELERATE = "accelerate_decelerate";
    private static final String ANTICIPATE = "anticipate";
    private static final String ANTICIPATE_OVERSHOOT = "anticipate_overshoot";
    private static final String BOUNCE = "bounce";
    private static final String CUBIC_BEZIER_ACCELERATION = "cubic_bezier_acceleration";
    private static final String CUBIC_BEZIER_DECELERATION = "cubic_bezier_deceleration";
    private static final String CUBIC_BEZIER_EXTREME_DECELERATION = "cubic_bezier_extreme_deceleration";
    private static final String CUBIC_BEZIER_FRICTION = "cubic_bezier_friction";
    private static final String CUBIC_BEZIER_RHYTHM = "cubic_bezier_rhythm";
    private static final String CUBIC_BEZIER_SHARP = "cubic_bezier_sharp";
    private static final String CUBIC_BEZIER_SMOOTH = "cubic_bezier_smooth";
    private static final String CUBIC_BEZIER_STANDARD = "cubic_bezier_standard";
    private static final String CUBIC_HERMITE = "cubic_hermite";
    private static final String CUSTOMIZED = "customized";
    private static final String CYCLE = "cycle";
    private static final String DECELERATE = "decelerate";
    private static final String INVALID = "invalid";
    private static final String LINEAR = "linear";
    private static final String OVERSHOOT = "overshoot";
    private static final String PHYSICAL_FLING = "physical_fling";
    private static final String PHYSICAL_SPRING = "physical_spring";
    private static final String SMOOTH_STEP = "smooth_step";
    private static final String SPRING = "spring";

    private static final String TYPE = "type";
    private static final String SET = "set";
    private static final String LINEAR_ANIMATOR = "linear_animator";
    private static final String ALPHA = "alpha";
    private static final String SCALE = "scale";
    private static final String ROTATE = "rotate";
    private static final String TRANSLATE = "translate";

    private static final String PERCENT_SELF = "%";
    private static final String PERCENT_RELATIVE_PARENT = "%p";

    /**
     * Loads an {@link AnimatorProperty} object from a resource
     *
     * @param context Application context used to access resources
     * @param value JSON representation of the AnimatorProperty
     * @return The AnimatorProperty object reference by the specified id
     */
    public static AnimatorGroup loadAnimatorProperty(Context context, Value value) {
        AnimatorGroup anim = null;
        if (value.isPrimitive()) {
            anim = handleString(context, value.getAsPrimitive().getAsString());
        } else if (value.isObject()) {
            anim = handleElement(context, value.getAsObject());
        } else {
            if (ProteusConstants.isLoggingEnabled()) {
                LogUtil.debug(TAG, "Could not load AnimatorProperty for : " + value.toString());
            }
        }
        return anim;
    }

    private static AnimatorGroup handleString(Context c, String value) {
        return null;
    }

    private static AnimatorGroup handleElement(Context context, ObjectValue value) {
        AnimatorGroup anim = null;
        String type = value.getAsString(TYPE);
        AnimatorPropertyProperties AnimatorPropertyProperties = null;
        if (SET.equalsIgnoreCase(type)) {
            AnimatorPropertyProperties = new AnimatorPropertySetProperties(value);
        } else if (ALPHA.equalsIgnoreCase(type)) {
            AnimatorPropertyProperties = new AlphaAnimProperties(value);
        } else if (SCALE.equalsIgnoreCase(type)) {
            AnimatorPropertyProperties = new ScaleAnimProperties(value);
        } else if (ROTATE.equalsIgnoreCase(type)) {
            AnimatorPropertyProperties = new RotateAnimProperties(value);
        } else if (TRANSLATE.equalsIgnoreCase(type)) {
            AnimatorPropertyProperties = new TranslateAnimProperties(value);
        } else if (LINEAR_ANIMATOR.equalsIgnoreCase(type)) {
            AnimatorPropertyProperties = new LinearAnimProperties(value);
        }
        if (null != AnimatorPropertyProperties) {
            anim = AnimatorPropertyProperties.instantiate(context);
        }
        return anim;
    }

    /**
     * loadInterpolator
     *
     * @param context Application context used to access resources
     * @param value Json representation of the Interpolator
     * @return The AnimatorProperty object reference by the specified id
     */
    public static int loadInterpolator(Context context, Value value) {
        int interpolator = 0;
        if (value.isPrimitive()) {
            interpolator = handleStringInterpolator(context, value.getAsString());
        } else if (value.isObject()) {
            interpolator = handleElementInterpolator(context, value.getAsObject());
        } else {
            if (ProteusConstants.isLoggingEnabled()) {
                LogUtil.debug(TAG, "Could not load interpolator for : " + value.toString());
            }
        }
        return interpolator;
    }

    private static int handleStringInterpolator(Context c, String value) {
        return 0;
    }

    private static int handleElementInterpolator(Context c, ObjectValue value) {
        int curveType = Animator.CurveType.LINEAR;
        String type = value.getAsString("type");
        switch (type) {
            case ACCELERATE:
                curveType = Animator.CurveType.ACCELERATE;
                break;
            case ACCELERATE_DECELERATE:
                curveType = Animator.CurveType.ACCELERATE_DECELERATE;
                break;
            case ANTICIPATE:
                curveType = Animator.CurveType.ANTICIPATE;
                break;
            case ANTICIPATE_OVERSHOOT:
                curveType = Animator.CurveType.ANTICIPATE_OVERSHOOT;
                break;
            case BOUNCE:
                curveType = Animator.CurveType.BOUNCE;
                break;
            case CUBIC_BEZIER_ACCELERATION:
                curveType = Animator.CurveType.CUBIC_BEZIER_ACCELERATION;
                break;
            case CUBIC_BEZIER_DECELERATION:
                curveType = Animator.CurveType.CUBIC_BEZIER_DECELERATION;
                break;
            case CUBIC_BEZIER_EXTREME_DECELERATION:
                curveType = Animator.CurveType.CUBIC_BEZIER_EXTREME_DECELERATION;
                break;
            case CUBIC_BEZIER_FRICTION:
                curveType = Animator.CurveType.CUBIC_BEZIER_FRICTION;
                break;
            case CUBIC_BEZIER_RHYTHM:
                curveType = Animator.CurveType.CUBIC_BEZIER_RHYTHM;
                break;
            case CUBIC_BEZIER_SHARP:
                curveType = Animator.CurveType.CUBIC_BEZIER_SHARP;
                break;
            case CUBIC_BEZIER_SMOOTH:
                curveType = Animator.CurveType.CUBIC_BEZIER_SMOOTH;
                break;
            case CUBIC_BEZIER_STANDARD:
                curveType = Animator.CurveType.CUBIC_BEZIER_STANDARD;
                break;
            case CUBIC_HERMITE:
                curveType = Animator.CurveType.CUBIC_HERMITE;
                break;
            case CUSTOMIZED:
                curveType = Animator.CurveType.CUSTOMIZED;
                break;
            case CYCLE:
                curveType = Animator.CurveType.CYCLE;
                break;
            case DECELERATE:
                curveType = Animator.CurveType.DECELERATE;
                break;
            case INVALID:
                curveType = Animator.CurveType.INVALID;
                break;
            case LINEAR:
                curveType = Animator.CurveType.LINEAR;
                break;
            case OVERSHOOT:
                curveType = Animator.CurveType.OVERSHOOT;
                break;
            case PHYSICAL_FLING:
                curveType = Animator.CurveType.PHYSICAL_FLING;
                break;
            case PHYSICAL_SPRING:
                curveType = Animator.CurveType.PHYSICAL_SPRING;
                break;
            case SMOOTH_STEP:
                curveType = Animator.CurveType.SMOOTH_STEP;
                break;
            case SPRING:
                curveType = Animator.CurveType.SPRING;
                break;
        }
        return curveType;
    }

    /**
     * Utility class to parse a string description of a size.
     */
    private static class Description {
        /**
         * One of AnimatorProperty.ABSOLUTE, AnimatorProperty.RELATIVE_TO_SELF, or
         * AnimatorProperty.RELATIVE_TO_PARENT.
         */
        public int type;

        /**
         * The absolute or relative dimension for this Description.
         */
        public float value;

        /**
         * Size descriptions can appear in three forms:
         * <ol>
         * <li>An absolute size. This is represented by a number.</li>
         * <li>A size relative to the size of the object being animated. This
         * is represented by a number followed by "%".</li> *
         * <li>A size relative to the size of the parent of object being
         * animated. This is represented by a number followed by "%p".</li>
         * </ol>
         *
         * @param value The Json value to parse
         * @return The parsed version of the description
         */
        static Description parseValue(Value value) {
            Description d = new Description();
            //d.type = AnimatorProperty.ABSOLUTE;
            d.value = 0;
            if (value != null && value.isPrimitive()) {
                if (value.getAsPrimitive().isNumber()) {
                    //d.type = AnimatorProperty.ABSOLUTE;
                    d.value = value.getAsPrimitive().getAsFloat();
                } else {
                    String stringValue = value.getAsPrimitive().getAsString();
                    if (stringValue.endsWith(PERCENT_SELF)) {
                        stringValue = stringValue.substring(0, stringValue.length() - PERCENT_SELF.length());
                        d.value = Float.parseFloat(stringValue) / 100;
                        //d.type = AnimatorProperty.RELATIVE_TO_SELF;
                    } else if (stringValue.endsWith(PERCENT_RELATIVE_PARENT)) {
                        stringValue = stringValue.substring(0, stringValue.length() - PERCENT_RELATIVE_PARENT.length());
                        d.value = Float.parseFloat(stringValue) / 100;
                        //d.type = AnimatorProperty.RELATIVE_TO_PARENT;
                    } else {
                        //d.type = AnimatorProperty.ABSOLUTE;
                        d.value = value.getAsPrimitive().getAsFloat();
                    }
                }
            }
            return d;
        }
    }

    private abstract static class AnimatorPropertyProperties {
        public static final String DETACH_WALLPAPER = "detachWallpaper";
        public static final String DURATION = "duration";
        public static final String FILL_AFTER = "fillAfter";
        public static final String FILL_BEFORE = "fillBefore";
        public static final String FILL_ENABLED = "fillEnabled";
        public static final String INTERPOLATOR = "interpolator";
        public static final String REPEAT_COUNT = "repeatCount";
        public static final String REPEAT_MODE = "repeatMode";
        public static final String START_OFFSET = "startOffset";
        public static final String Z_ADJUSTMENT = "zAdjustment";

        Boolean detachWallpaper;
        Long duration;
        Boolean fillAfter;
        Boolean fillBefore;
        Boolean fillEnabled;
        Value interpolator;
        Integer repeatCount;
        Integer repeatMode;
        Long startOffset;
        Integer zAdjustment;

        public AnimatorPropertyProperties(ObjectValue value) {
            detachWallpaper = value.getAsBoolean(DETACH_WALLPAPER);
            duration = value.getAsLong(DURATION);
            fillAfter = value.getAsBoolean(FILL_AFTER);
            fillBefore = value.getAsBoolean(FILL_BEFORE);
            fillEnabled = value.getAsBoolean(FILL_ENABLED);
            interpolator = value.get(INTERPOLATOR);
            repeatCount = value.getAsInteger(REPEAT_COUNT);
            repeatMode = value.getAsInteger(REPEAT_MODE);
            startOffset = value.getAsLong(START_OFFSET);
            zAdjustment = value.getAsInteger(Z_ADJUSTMENT);
        }

        public AnimatorGroup instantiate(Context c) {
            AnimatorGroup anim = createAnimatorProperty(c);
            if (null != anim) {
                if (null != detachWallpaper) {
                    //anim.setDetachWallpaper(detachWallpaper);
                }

                if (null != duration) {
                    anim.setDuration(duration);
                }

                if (null != fillAfter) {
                    //anim.setFillAfter(fillAfter);
                }

                if (null != fillBefore) {
                    //anim.setFillBefore(fillBefore);
                }

                if (null != fillEnabled) {
                    //anim.setFillEnabled(fillEnabled);
                }

                if (null != interpolator) {
                    int curveType = Animator.CurveType.LINEAR;
                    try {
                        curveType = loadInterpolator(c, interpolator);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (Animator.CurveType.LINEAR != curveType) {
                        anim.setCurveType(curveType);
                    }
                }

                if (null != repeatCount) {
                    anim.setLoopedCount(repeatCount);
                }

                if (null != repeatMode) {
                    //anim.setRepeatMode(repeatMode);
                }

                if (null != startOffset) {
                    anim.setDelay(startOffset);
                }

                if (null != zAdjustment) {
                    //anim.setZAdjustment(zAdjustment);
                }
            }
            return anim;
        }

        abstract AnimatorGroup createAnimatorProperty(Context c);
    }

    private static class AnimatorPropertySetProperties extends AnimatorPropertyProperties {
        public static final String SHARE_INTERPOLATOR = "shareInterpolator";
        public static final String CHILDREN = "children";

        Boolean shareInterpolator;
        Value children;

        public AnimatorPropertySetProperties(ObjectValue value) {
            super(value);
            shareInterpolator = value.getAsBoolean(SHARE_INTERPOLATOR);
            children = value.get(CHILDREN);
        }

        @Override
        AnimatorGroup createAnimatorProperty(Context c) {
            AnimatorGroup animatorGroup = new AnimatorGroup();
            AnimatorGroup.Builder builder = animatorGroup.build();
            if (null != children) {
                if (children.isArray()) {
                    Iterator<Value> iterator = children.getAsArray().iterator();
                    while (iterator.hasNext()) {
                        AnimatorGroup animatorProperty = null;
                        try {
                            animatorProperty = loadAnimatorProperty(c, iterator.next());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        if (null != animatorProperty) {
                            builder.addAnimators(animatorProperty);
                        }
                    }
                } else if (children.isObject() || children.isPrimitive()) {
                    AnimatorGroup animatorProperty = null;
                    try {
                        animatorProperty = loadAnimatorProperty(c, children);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (null != animatorProperty) {
                        builder.addAnimators(animatorProperty);
                    }
                }
            }
            return animatorGroup;
        }
    }

    private static class AlphaAnimProperties extends AnimatorPropertyProperties {
        public static final String FROM_ALPHA = "fromAlpha";
        public static final String TO_ALPHA = "toAlpha";

        public Float fromAlpha;
        public Float toAlpha;

        public AlphaAnimProperties(ObjectValue value) {
            super(value);
            fromAlpha = value.getAsFloat(FROM_ALPHA);
            toAlpha = value.getAsFloat(TO_ALPHA);
        }

        @Override
        AnimatorGroup createAnimatorProperty(Context c) {
            AnimatorProperty animatorProperty = new AnimatorProperty();
            AnimatorGroup animatorGroup = new AnimatorGroup();
            if (fromAlpha != null && toAlpha != null) {
                animatorProperty.alphaFrom(fromAlpha).alpha(toAlpha);
            } else {
                animatorProperty.alphaFrom(1).alpha(1);
            }
            animatorGroup.runSerially(animatorProperty);
            return animatorGroup;
        }
    }

    private static class ScaleAnimProperties extends AnimatorPropertyProperties {
        public static final String FROM_X_SCALE = "fromXScale";
        public static final String TO_X_SCALE = "toXScale";
        public static final String FROM_Y_SCALE = "fromYScale";
        public static final String TO_Y_SCALE = "toYScale";
        public static final String PIVOT_X = "pivotX";
        public static final String PIVOT_Y = "pivotY";

        public Float fromXScale;
        public Float toXScale;
        public Float fromYScale;
        public Float toYScale;
        public Value pivotX;
        public Value pivotY;

        public ScaleAnimProperties(ObjectValue value) {
            super(value);
            fromXScale = value.getAsFloat(FROM_X_SCALE);
            toXScale = value.getAsFloat(TO_X_SCALE);
            fromYScale = value.getAsFloat(FROM_Y_SCALE);
            toYScale = value.getAsFloat(TO_Y_SCALE);
            pivotX = value.get(PIVOT_X);
            pivotY = value.get(PIVOT_Y);
        }

        @Override
        AnimatorGroup createAnimatorProperty(Context c) {
            AnimatorProperty animatorProperty = new AnimatorProperty();
            AnimatorGroup animatorGroup = new AnimatorGroup();
            if (fromXScale != null && toXScale != null && fromYScale != null && toYScale != null) {
                animatorProperty.scaleXFrom(fromXScale).scaleX(toXScale).scaleYFrom(fromYScale).scaleY(toYScale);
            } else {
                animatorProperty.scaleXFrom(1).scaleX(1).scaleYFrom(1).scaleY(1);
            }
            animatorGroup.runSerially(animatorProperty);
            return animatorGroup;
        }
    }

    private static class TranslateAnimProperties extends AnimatorPropertyProperties {
        public static final String FROM_X_DELTA = "fromXDelta";
        public static final String TO_X_DELTA = "toXDelta";
        public static final String FROM_Y_DELTA = "fromYDelta";
        public static final String TO_Y_DELTA = "toYDelta";

        public Value fromXDelta;
        public Value toXDelta;
        public Value fromYDelta;
        public Value toYDelta;

        public TranslateAnimProperties(ObjectValue value) {
            super(value);
            fromXDelta = value.get(FROM_X_DELTA);
            toXDelta = value.get(TO_X_DELTA);
            fromXDelta = value.get(FROM_Y_DELTA);
            toYDelta = value.get(TO_Y_DELTA);
        }

        @Override
        AnimatorGroup createAnimatorProperty(Context c) {
            Description fromXDeltaDescription = Description.parseValue(fromXDelta);
            Description toXDeltaDescription = Description.parseValue(toXDelta);
            Description fromYDeltaDescription = Description.parseValue(fromYDelta);
            Description toYDeltaDescription = Description.parseValue(toYDelta);

            //return new TranslateAnimatorProperty(fromXDeltaDescription.type, fromXDeltaDescription.value,
            // toXDeltaDescription.type, toXDeltaDescription.value, fromYDeltaDescription.type,
            // fromYDeltaDescription.value, toYDeltaDescription.type, toYDeltaDescription.value);
            return null;
        }
    }

    private static class LinearAnimProperties extends AnimatorPropertyProperties {
        public static final String FROM_X = "fromX";
        public static final String TO_X = "toX";
        public static final String FROM_Y = "fromY";
        public static final String TO_Y = "toY";

        public Float fromX;
        public Float toX;
        public Float from_Y;
        public Float toY;

        public LinearAnimProperties(ObjectValue value) {
            super(value);
            fromX = value.getAsFloat(FROM_X);
            toX = value.getAsFloat(TO_X);
            from_Y = value.getAsFloat(FROM_Y);
            toY = value.getAsFloat(TO_Y);
        }

        @Override
        AnimatorGroup createAnimatorProperty(Context c) {
            AnimatorProperty animatorProperty = new AnimatorProperty();
            animatorProperty.moveFromX(fromX).moveToX(toX).moveFromY(from_Y).moveToY(toY);
            AnimatorGroup animatorGroup = new AnimatorGroup();
            animatorGroup.runSerially(animatorProperty);
            return animatorGroup;
        }
    }

    private static class RotateAnimProperties extends AnimatorPropertyProperties {
        public static final String FROM_DEGREES = "fromDegrees";
        public static final String TO_DEGREES = "toDegrees";
        public static final String PIVOT_X = "pivotX";
        public static final String PIVOT_Y = "pivotY";

        public Float fromDegrees;
        public Float toDegrees;
        public Value pivotX;
        public Value pivotY;

        public RotateAnimProperties(ObjectValue value) {
            super(value);
            fromDegrees = value.getAsFloat(FROM_DEGREES);
            toDegrees = value.getAsFloat(TO_DEGREES);
            pivotX = value.get(PIVOT_X);
            pivotY = value.get(PIVOT_Y);
        }

        @Override
        AnimatorGroup createAnimatorProperty(Context c) {
            AnimatorProperty animatorProperty = new AnimatorProperty();
            animatorProperty.rotate(toDegrees);
            AnimatorGroup animatorGroup = new AnimatorGroup();
            animatorGroup.runSerially(animatorProperty);
            return animatorGroup;
        }
    }

    private abstract static class InterpolatorProperties {
        public InterpolatorProperties(Value value) {
        }

        abstract Animator.TimelineCurve createInterpolator(Context c);
    }

    private static class PathInterpolatorProperties extends InterpolatorProperties {
        public static final String CONTROL_X1 = "controlX1";
        public static final String CONTROL_Y1 = "controlY1";
        public static final String CONTROL_X2 = "controlX2";
        public static final String CONTROL_Y2 = "controlY2";

        public Float controlX1;
        public Float controlY1;
        public Float controlX2;
        public Float controlY2;

        public PathInterpolatorProperties(ObjectValue parser) {
            super(parser);
            controlX1 = parser.getAsFloat(CONTROL_X1);
            controlY1 = parser.getAsFloat(CONTROL_Y1);
            controlX2 = parser.getAsFloat(CONTROL_X2);
            controlY2 = parser.getAsFloat(CONTROL_Y2);
        }

        Animator.TimelineCurve createInterpolator(Context c) {
            if (null != controlX2 && null != controlY2) {
                //return new PathInterpolator(controlX1, controlY1, controlX2, controlY2);
            } else {
                //return new PathInterpolator(controlX1, controlY1);
            }
            return null;
        }
    }

    private static class AnticipateInterpolatorProperties extends InterpolatorProperties {
        public static final String TENSION = "tension";
        public Float tension;

        public AnticipateInterpolatorProperties(ObjectValue parser) {
            super(parser);
            tension = parser.getAsFloat(TENSION);
        }

        Animator.TimelineCurve createInterpolator(Context c) {
            //return new AnticipateInterpolator(tension);
            return null;
        }
    }

    private static class OvershootInterpolatorProperties extends InterpolatorProperties {
        public static final String TENSION = "tension";
        public Float tension;

        public OvershootInterpolatorProperties(ObjectValue parser) {
            super(parser);
            tension = parser.getAsFloat(TENSION);
        }

        Animator.TimelineCurve createInterpolator(Context c) {
            //return tension == null ? new OvershootInterpolator() : new OvershootInterpolator(tension);
            return null;
        }
    }

    private static class AnticipateOvershootInterpolatorProperties extends InterpolatorProperties {
        public static final String TENSION = "tension";
        public static final String EXTRA_TENSION = "extraTension";

        public Float tension;
        public Float extraTension;

        public AnticipateOvershootInterpolatorProperties(ObjectValue parser) {
            super(parser);
            tension = parser.getAsFloat(TENSION);
            extraTension = parser.getAsFloat(EXTRA_TENSION);
        }

        Animator.TimelineCurve createInterpolator(Context c) {
            //return null == tension ? new AnticipateOvershootInterpolator() : (null == extraTension
            // ? new AnticipateOvershootInterpolator(tension)
            // : new AnticipateOvershootInterpolator(tension, extraTension));
            return null;
        }
    }

    private static class CycleInterpolatorProperties extends InterpolatorProperties {
        public static final String CYCLES = "cycles";
        public Float cycles;

        public CycleInterpolatorProperties(ObjectValue parser) {
            super(parser);
            cycles = parser.getAsFloat(CYCLES);
        }

        Animator.TimelineCurve createInterpolator(Context c) {
            //return new CycleInterpolator(cycles);
            return null;
        }
    }
}
